
__author__="Action Farsi"
__date__ ="$21-giu-2009 21.18.26$"

import random as rnd
import ability
import combat

from copy import copy
from utilities import *

import globals

import libtcodpy as lt

import logger
logging = logger.the_log

class Entity():
    def __init__(self, **keyw):
        self.name = "Name"
        self.ID = rnd.random()
        if "name" in keyw:
            self.name = keyw["name"]
        if "pos" in keyw:
            self.pos = keyw["pos"]
        self.symbol = "!"
        self.color = lt.black
        self.last_turn = 0
        self.speed = 0

    def init(self):
        pass

    def click(self, time_step = 1):
        pass

    def think(self):
        pass

    def __repr__(self):
        return self.name

class Item(Entity):
    def __init__(self, **keyw):
        self.name = "Gizmo"
        Entity.__init__(self, **keyw)
        self.description = "An unidentified item"
        self.is_weapon = False
        self.is_usable = False
        self.is_wearable = False
        self.type = Set()
        self.symbol = "!"

    def drink(self,target):
        target.life = max(target.life + 10, 100)
        s.owner.remove_ent(s)
        del s.owner

    def set_usable(item, use = None):
        def do_nothing(s,target):
            print "Nothing happens"

        item.is_usable = True
        if use:
            item.use = use
        else:
            item.use = do_nothing

    def set_wearable(item, armor_pt = 2):
        item.is_wearable = True
        item.armor_pt = armor_pt

class Object(Entity):
    def __init__(self, **keyw):
        Entity.__init__(self, **keyw)
        self.name = "Object"
        self.is_passable = False
        self.symbol = "/"

    def interact(self):
        pass


class Actor(Entity):
    def __init__(self, **keyw):
        Entity.__init__(self, **keyw)
        
        self.symbol = 'A'
        self.is_passable = False
        self.visibility = True
        self.is_dead = False

        self.name = "Actor"
        self.description = "Someone"
        self.action_pts = 0

        self.life = 10
        self.maxlife = 10
        self.speed = 5

        self.owner = None
        self.inventory = []
        
        self.stats = [1,1,1,1,1,1]
        self.skills = [1,1,1,1,1,1]
        
        self.att_bonus = [1,1,1,1,1,1]
        self.def_bouns = [1,1,1,1,1,1]

        self.abilities = []
        self.slots = []

        # Temporaney
        self.target = None
        self.targets = []
        self.curr_item = "Punch"
        self.c_slot = []

    ## Actions: Move, attack, fire, get, drop, talk

    ## TODO def timescale
    def move(self, direction):
        # Find out if the movement is possible
        new_pos = (self.pos[0] + direction[0], self.pos[1] + direction[1])
        b = reduce(lambda x,y: x and self.bump(y), globals.c_lev.get_entity(new_pos, filtr = Actor), True)
        if not b:
            return False
        if globals.c_lev.is_walkable(new_pos):
            lt.map_set_properties(globals.c_lev.tmap,self.pos[0],self.pos[1],True,True)
            self.pos = new_pos
            lt.map_set_properties(globals.c_lev.tmap,self.pos[0],self.pos[1],self.visibility,True)

            # Check time energy
            self.action_pts += 1

            return True

    def attack(self,target):
        "Return True if the target is defeated"
        logging.info("%s attack %s"%(self.name,target.name))
        att_skill = self.att_skill
        dfn_skill = self.def_skill
        att = sum( (s * m *(1+b) for s,m,b in zip(self.stats, att_skill , self.att_bonus) ) ) * (0.8 + rnd.random() * 0.40)
        dfn = sum( (s * m *(1+b) for s,m,b in zip(target.stats,dfn_skill, target.def_bonus )) ) * (0.8 + rnd.random() * 0.40)
        logging.debug("Att %6.2f - Def %6.2f"%(att,dfn))
        if att > dfn:
            #hit
            target.get_hit(self,2 + self.dam_mod - target.def_mod)
            return False
        else:
            # miss
            return False

    def fire(self, target):
        logging.info("%s fires at %s"%(self.name, target))
        pass

    def click(self, time_step = 1):
        self.action_pts = max(0, self.action_pts - time_step)

    def think(self):
        self.targets = globals.c_lev.get_entity(self.pos, near = 50, exclude = self, filtr = Actor )
        #self.targets = self.targets.append(globals.c_lev.get_entity(self.pos, near = 50, exclude = self, filtr = Object ))
        if self.target:
            if self.target.is_dead:
                self.target = None

    def get_hit(self, origin, damage = 0):
        self.life = self.life - damage
        if self.life < 0:
            self.die()
        return True

    def die(self):
        self.is_dead = True
        logger.info("%s died"%self.name)


    def bump(self,target):
        return target.is_passable

    def pick_up(self, target = None):
        """ Pick up the first item, or the given item on the same pos of the actor
        if nothing is picked return None
        else return the entity
        """
        print "pickup"
        item_here = filter(lambda x: isinstance(x,Item), globals.c_lev.get_entity(pos = self.pos))
        if not item_here:
            logging.debug("No Item in %s"%self.pos)
            return False
        # else
        if not (target and target in item_here):
            target = item_here[0]

        target.owner.remove_ent(target)
        self.add_ent(target)
        logging.info("%s picked up %s"%(self.name, target))
        return True


    def set_ability(self, ability_n, slot_n, ability = None):
        try:
            return self.slots[slot_n].set_ability(self.abilities[ability_n])
        except IndexError:
            return False

    def init(self):
        self.autoset_abilities()
        self.update_bonus()

    def update_bonus(self):
        # TODO update for abilities
        # TODO implements slots
        #ab = [a.ability for a in self.slots if "attack_close" in a.ability.type]
        #if ab:
        #    self.att_skill = ab[0].att_skill
        #    self.def_skill = ab[0].def_skill


        #self.att_bonus = (0,0,0,0,0,0)
        #self.dam_mod = 0
        #for ab in (a.ability for a in self.slots if "weapon" in a.ability.type):
        #    self.dam_mod += ab.dam_mod
        #    self.att_bonus = [a+b for a,b in zip(self.att_bonus, ab.att_bonus)]

        #self.def_bonus = (0,0,0,0,0,0)
        #self.def_mod = 0
        #for ab in (a.ability for a in self.slots if "defence" in a.ability.type):
        #    self.def_mod += ab.def_mod
        #    self.def_bonus = [a+b for a,b in zip(self.def_bonus, ab.def_bonus)]

        # TODO update for item
        pass

    def drop(self, target = None):
        if not target and self.inventory:
            target = self.inventory[0]
        if not target or target not in self.inventory :
            return False

        self.remove_ent(target)
        target.pos = self.pos
        globals.c_lev.add_ent(target)
        logging.info("%s dropped %s"%(self.name, target))
        return True

    def use_slot(self, slot, target = None):
        if slot.ability.kind == "close":
            if not isinstance(target, Actor):
                return False
            if target.is_dead:
                return False
            if dist(self.pos, target.pos)>1:
                return False
            logging.info("%s attack %s"%(self.name,target.name))
            combat.attack(self, target, slot.ability, slot.item)
            return True

        if slot.ability.kind == "range":
            if not isinstance(target, Actor):
                return False
            if target.is_dead:
                return False
            if dist(self.pos, target.pos)>5:
                return False
            logging.info("%s shots %s with %s"%(self.name,target.name, slot.name))
            combat.attack(self, target, slot.ability, slot.item)
            return True


    ## Inventory management
    def remove_ent(self, ent):
        self.inventory.remove(ent)
        del ent.owner

    def add_ent(self, ent):
        self.inventory.append(ent)
        ent.owner = self

    def inventory_list(self, text = False):
        return [ "%s - %s"%(chr(i+97),item.name) for i,item in enumerate(self.inventory)]

    def get_item(self, code):
        if 0 <= code < len(self.inventory):
            return self.inventory[code]
        return None

    def get_slot(self,code):
        if 0 <= code < len(self.slots):
            return self.slots[code]
        return None

    def get_ability(self,code):
        if 0 <= code < len(self.abilities):
            return self.abilities[code]
        return None

    def autoset_abilities(self):
        for j,s in enumerate(self.slots):
            for i,a in enumerate(self.abilities):
                if a.type < s.type:
                    self.set_ability(i, j)
                    break

class NPC(Actor):
    def __init__(self,  **keyw):
        Actor.__init__(self, **keyw)
        self.drop_list = []

    def think(self):
        player = globals.player
        if self.is_dead:
            return
        ## TODO Look around and update target list
        #if self.action_pts > 0:
        #    return
        path = lt.path_new_using_map(globals.c_lev.tmap)
        lt.path_compute(path, self.pos[0], self.pos[1], player.pos[0], player.pos[1])
        if lt.path_size(path) > 0:
            new_pos = lt.path_walk(path, True)
            if new_pos[0]:
                self.move((new_pos[0]-self.pos[0],new_pos[1]-self.pos[1]))
            return
        ## Attack routine
        # print dist(player.pos,self.pos)
        if dist(player.pos,self.pos)<2:
            #combat.attack(player)
            return


    def bump(self,target):
        #if target is player:
        #if isinstance(target,Actor):
        #    if [a.ability for a in self.slots if "close" in a.ability.type]:
        #        return self.attack(target)
        #else:
            return Actor.bump(self,target)

    def die(self):
        self.is_dead = True
        logging.info("%s died"%self.name)
        for i in self.inventory:
            self.drop(i)
        self.owner.remove_ent(self)
        self.owner = None

class Super(Actor):
    def __init__(self, **keyw):
        Actor.__init__(self, **keyw)
        self.name = "Action Farsi"
        self.symbol = '@'
        self.stats =[3,3,3,2,1,3]
        self.skills = [4,1,3,2,2,2]  #Warr, Tech,  Mutant, Magic, Mental
        self.slots = [#copy(ability.slots["right_hand"]),
                      copy(ability.slots["combat_style"]),
                      copy(ability.slots["fire_ability"]),
                      #copy(ability.slots["defence"]),
                      #copy(ability.slots["special"]),
                      #copy(ability.slots["special"]),
                      ]
        self.speed = 2

    def bump(self, target):
        if isinstance(target,Actor):
            #if [a.ability for a in self.slots if "close" in a.ability.type]:
            self.use_slot(self.slots[0],target)
            return False
        else:
            return True

    def fire(self, target):
        if [a for a in self.slots if "range" in a.ability.type]:
            return self.fire(target)


def get_potion(pos):
    

    pot = Item(pos, name = "Potion")
    Item.set_usable(pot, drink)
    return pot

def get_knife(pos):
    knife = Item(pos, name = "Knife")
    Item.set_weapon(knife, damage = 2)
    knife.symbol = '|'
    return knife

def get_cape(pos):
    cape = Item(pos, name = "Cape")
    Item.set_wearable(cape, 5)
    cape.symbol ='^'
    return cape

def get_key(pos):
    key = Item(pos, name = "Key")
    key.idcode = '1234'
    key.symbol = '%'
    return key
            
class Door(Object):
    def __init__(self, **keyw):
        Object.__init__(self, **keyw)
        self.name = "door"
        self.is_passable = False
        self.symbol = "/"
        self.color = lt.Color(40,20,47)
        self.is_open = False
    
    def interact(self):
        if self.is_open:
            self.is_open = False
            self.is_passable = False
            self.symbol = '/'
        else:
            self.is_open = True
            self.is_passable = True
            self.symbol = '^'

def tell_class(entity):
    """ Return a string with the description of the class"""
    ## TODO
    #entity.skill
    pass

raws = globals.raws   
r = None

def parse(raws_file):    
    def end():
        if r is not None:
            raws[r.name] = r
            
    def raw(type, name):
        global r
        end()
        if type in raws:
            r = copy(raws[type])
        else:
            r = EntityRaw(type)
        r.name = name
    
    def symb(symbol, color = lt.black):
        r.symbol = symbol
        r.color = color
        
    def weapon(damage):
        r.is_weapon = True
        r.damage = damage
        r.type.add("range")

    def armor(armor = 2):
        r.is_wearable = True
        r.armor_pt = armor
    
    def stats(sts):
        r.stats = (sts)
    
    ## TODO Check existence f abilities and slots
    def slot(slotn):
        if not hasattr(r,"slots"):
            r.slots = []
        r.slots.append(slotn)
        
    def ability(ability):
        if not hasattr(r,"slots"):
            r.abilities = []
        r.abilities.append(ability)
    
    
    execfile(raws_file)
    
    end()
    
class EntityRaw():
    clss = {"actor": Actor,
            "item": Item,
            "object": Object,
            "entity": Entity,
            "NPC": NPC,}
            
    def __init__(self,cl):
        self.cl = cl
    
    def get(self):
        entity = EntityRaw.clss[self.cl](pos = (0,0))
        entity.__dict__.update(self.__dict__)
        entity.init()
        return entity
 